package com.ljh.aiplatform.scoring;

import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.ljh.aiplatform.common.ErrorCode;
import com.ljh.aiplatform.config.RedissionConfig;
import com.ljh.aiplatform.exception.BusinessException;
import com.ljh.aiplatform.exception.ThrowUtils;
import com.ljh.aiplatform.manager.AiManager;
import com.ljh.aiplatform.model.dto.ai.QuestionAnswerDTO;
import com.ljh.aiplatform.model.dto.question.QuestionContentDTO;
import com.ljh.aiplatform.model.dto.question.QuestionContentOption;
import com.ljh.aiplatform.model.entity.App;
import com.ljh.aiplatform.model.entity.Question;
import com.ljh.aiplatform.model.entity.ScoringResult;
import com.ljh.aiplatform.model.entity.UserAnswer;
import com.ljh.aiplatform.model.enums.AppTypeEnum;
import com.ljh.aiplatform.service.AppService;
import com.ljh.aiplatform.service.QuestionService;
import com.ljh.aiplatform.service.ScoringResultService;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * Ai角色测评策略
 */
@ScoringStrategyConfig(appType = 1, scoringStrategy = 1)
public class AiTestScoringStrategy implements ScoringStrategy {

    @Resource
    private QuestionService questionService;
    @Resource
    private AppService appService;

    @Resource
    private AiManager aiManager;

    @Resource
    private RedissonClient redissonClient;

    private static final String AI_ANSWER_LOCK = "AI_ANSWER_LOCK";

    // 创建一个缓存实例
    Cache<String, String> cache = Caffeine.newBuilder()
            // 设置缓存的最大容量
            .initialCapacity(1024)
            // 设置缓存项的过期时间
            .expireAfterWrite(10, TimeUnit.MINUTES)
            // 构建缓存
            .build();

    private static final String SystemPrompt = "你是一位严谨的判题专家，我会给你如下信息：\n" +
            "```\n" +
            "应用名称，\n" +
            "【【【应用描述】】】，\n" +
            "题目和用户回答的列表：格式为 [{\"title\": \"题目\",\"answer\": \"用户回答\"}]\n" +
            "```\n" +
            "\n" +
            "请你根据上述信息，按照以下步骤来对用户进行评价：\n" +
            "1. 要求：需要给出一个明确的评价结果，包括评价名称（尽量简短）和评价描述（尽量详细，但不多于150个字）\n" +
            "2. 严格按照下面的 json 格式输出评价名称和评价描述\n" +
            "```\n" +
            "{\"resultName\": \"评价名称\", \"resultDesc\": \"评价描述\"}\n" +
            "```\n" +
            "3. 返回格式必须为 JSON 对象\n";

    /**
     * 封装用户的prompt
     *
     * @param app
     * @param question
     * @param choices
     * @return
     */
    public String getUserPrompt(App app, Question question, List<String> choices) {
        ThrowUtils.throwIf(app == null, ErrorCode.NOT_FOUND_ERROR, "应用不存在");
        ThrowUtils.throwIf(question == null, ErrorCode.NOT_FOUND_ERROR, "题目不存在");
        ThrowUtils.throwIf(choices == null || choices.size() == 0, ErrorCode.PARAMS_ERROR, "用户回答不能为空");
        StringBuilder userPrompt = new StringBuilder();
        userPrompt.append(app.getAppName());
        userPrompt.append(app.getAppDesc());
        List<QuestionAnswerDTO> questionAnswerDTOList = new ArrayList<>();
        List<QuestionContentDTO> contentDTOList = JSONUtil.toList(question.getQuestionContent(), QuestionContentDTO.class);
        for (int i = 0; i < choices.size(); i++) {
            QuestionAnswerDTO questionAnswerDTO = new QuestionAnswerDTO();
            questionAnswerDTO.setTitle(contentDTOList.get(i).getTitle());
            questionAnswerDTO.setUserAnswer(choices.get(i));
            questionAnswerDTOList.add(questionAnswerDTO);
        }
        userPrompt.append(JSONUtil.toJsonStr(questionAnswerDTOList));
        return userPrompt.toString();
    }


    @Override
    public UserAnswer doScoring(List<String> choices, App app) {
        //定义分布式锁
        final RLock lock = redissonClient.getLock(AI_ANSWER_LOCK);
        //1: 根据appid查询题目(题目表）
        Long appId = app.getId();
        //2：查询缓存
        final String jsonChoices = JSONUtil.toJsonStr(choices);
        String cacheKey = getCacheKey(appId, jsonChoices);
        String cacheIfPresent = cache.getIfPresent(cacheKey);
        if (StringUtils.isNotBlank(cacheIfPresent)) {
            return buildUserAnswer(cacheIfPresent, appId, app.getScoringStrategy(), app.getAppType(), jsonChoices);
        }
        try {
            //判断是否抢到锁，如果抢到，就执行下面的逻辑，没有抢到就直接抛异常
            final boolean tryLock = lock.tryLock(5, -1, TimeUnit.SECONDS);
            if(tryLock){
                throw new BusinessException(ErrorCode.SYSTEM_ERROR,"系统缓存异常");
            }
            LambdaQueryWrapper<Question> lambdaQueryWrapper1 = new LambdaQueryWrapper<>();
            lambdaQueryWrapper1.eq(Question::getAppId, appId);
            Question question = questionService.getOne(lambdaQueryWrapper1);
            //3.1: 封装userPrompt 并且调用AI接口
            String userPrompt = getUserPrompt(app, question, choices);
            String result = aiManager.doSyncStableRequest(SystemPrompt, userPrompt);
            int startIndex = result.indexOf("{");
            int endIndex = result.lastIndexOf("}");
            String jsonStr = result.substring(startIndex, endIndex + 1);
            //3.2 调用Ai接口之后，需要存入本地缓存
            cache.put(cacheKey, jsonStr);
            //4：构造返回值，填充答案对象的属性
            return buildUserAnswer(jsonStr, appId, app.getScoringStrategy(), app.getAppType(), jsonChoices);
        } catch (InterruptedException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        } finally {
            //判断是否是自己的锁，只有自己的锁才能释放
            if(lock.isHeldByCurrentThread()){
                System.out.println("unlock"+Thread.currentThread().getId());
                lock.unlock();
            }
        }
    }

    /**
     * 构建缓存的key为 应用id + 用户回答 并且用md5加密算法进行压缩
     *
     * @param appId
     * @param choices
     * @return
     */
    private String getCacheKey(Long appId, String choices) {
        return DigestUtil.md5Hex(appId + choices);
    }

    private UserAnswer buildUserAnswer(String jsonStr, Long appId, Integer scoringStrategy, Integer appType, String jsonChoices) {
        UserAnswer userAnswer = new UserAnswer();
        userAnswer = JSONUtil.toBean(jsonStr, UserAnswer.class);
        userAnswer.setAppId(appId);
        userAnswer.setAppType(appType);
        userAnswer.setScoringStrategy(scoringStrategy);
        userAnswer.setChoices(jsonChoices);
        return userAnswer;
    }
}
